using System;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.HtmlControls;
using System.ComponentModel;
using System.Text;

namespace BizElements.Web
{
    #region Documentation.
    /// <summary>
	/// Button control that opens a popup/lookup page from which a value is selected.
	/// </summary>
    /// <remarks><b>PopupButton</b> control is used to select a value from a popup window specified in
    /// the <see cref="PopupUrl"/> property. Popup windows invokes generated client-side script 
    /// to set the selected value into PopupButton control and initiate <b>PostBack</b>. On server 
    /// the <see cref="PopupButton.ItemSelected"/> event is raised which delivers the selected value in the 
    /// <see cref="System.Web.UI.WebControls.CommandEventArgs.CommandArgument"/> object.</remarks>
    #endregion
    [DefaultProperty("Text")]
    [DefaultEvent("ItemSelected")]
    [ToolboxData("<{0}:PopupButton runat=server></{0}:PopupButton>")]
	[DesignerAttribute(typeof(PopupButtonDesigner), typeof(System.ComponentModel.Design.IDesigner))]
    public class PopupButton : WebControl, ISearchPageClient, INamingContainer
	{
        // Selected value is available only in ItemSelected event. The selected values is not maintained
        // between postbacks. This is the reason why there is no SelectedValue getter.

		#region FieldPermissions.

		HtmlInputButton lookupBtn = new HtmlInputButton();
		TextBox selectedValueTxt = new TextBox();
		HtmlGenericControl div = new HtmlGenericControl("div");
		HtmlGenericControl iframe = new HtmlGenericControl("iframe");
		Button autoSubmitBtn = new Button();
        TextBox txtPopupUrl = new TextBox();
        int popupLeftPx;
        int popupTopPx;

		#endregion

        #region CTor & CreateChildControls.

        /// <summary>
		/// Initializes a new instance of the <see cref="PopupButton"/> class.
		/// </summary>
		public PopupButton()
		{
            this.selectedValueTxt.Style["display"] = "none";
            this.autoSubmitBtn.Style["display"] = "none";
            this.txtPopupUrl.Style["display"] = "none";

            this.div.Style["position"] = "absolute";
            this.div.Style["z-index"] = "-1";
            this.div.Style["visibility"] = "hidden";
            this.popupLeftPx = 50;
            this.popupTopPx = 50;
			
			this.iframe.Attributes["frameBorder"] = "0";
			this.iframe.Attributes["width"] = "600px";
			this.iframe.Attributes["height"] = "500px";
			this.div.Controls.Add(this.iframe);
		}

        /// <summary>
        /// Called by the ASP.NET page framework to notify server controls that use composition-based implementation to create any child controls they contain in preparation for posting back or rendering. 
        /// </summary>
        protected override void CreateChildControls()
        {
            this.Controls.Clear();

            this.Controls.Add(this.lookupBtn);
            this.Controls.Add(this.selectedValueTxt);
            this.Controls.Add(this.autoSubmitBtn);
            this.Controls.Add(this.div);
            this.Controls.Add(this.txtPopupUrl);
        }

		#endregion

        #region OnInit. Register FireEventIfRowSelected on Page.LoadComplete. RegisterClientScriptResource.

        /// <summary>
        /// Raises the <b>Init</b> event.
        /// </summary>
        /// <param name="e">An <b>EventArgs</b> object that contains the event data.</param>
        protected override void OnInit(EventArgs e)
        {
            base.OnInit(e);
            Page.LoadComplete += new EventHandler(this.FireEventIfRowSelected);
            Page.ClientScript.RegisterClientScriptResource(this.GetType(), "BizElements.Web.BoundLookup.js");
        }

        #endregion

        #region Control properties.

        /// <summary>
		/// Gets or sets the access key (underlined letter) that allows quick navigation to the Web server control.
		/// </summary>
		[Bindable(false), Category("Behavior"),
		Description("Access key (underlined letter) that allows quick navigation to the Web server control.")]
        [DefaultValue(null)]
		public override string AccessKey
		{
			get {return this.lookupBtn.Attributes["accessKey"];}
			set	{this.lookupBtn.Attributes["accessKey"] = value;}
		}

		/// <summary>
		/// Gets or sets the CSS Class name applied to the button.
		/// </summary>
		[Bindable(false), Category("Appearance"),
		Description("CSS Class name applied to the button.")]
        [DefaultValue(null)]
		public override string CssClass
		{
			get {return this.lookupBtn.Attributes["class"];}
			set	{this.lookupBtn.Attributes["class"] = value;}
		}

		/// <summary>
		/// Gets or sets the height of the popup window/iframe.
		/// </summary>
		[Bindable(false), Category("Appearance"),
		Description("Height of the popup window/iframe.")]
        [DefaultValue(typeof(Unit), "500px")]
		public virtual Unit PopupHeight
		{
			get {return Unit.Parse(this.iframe.Attributes["height"]);}
			set {this.iframe.Attributes["height"] = value.ToString();}
		}

        /// <summary>
        /// Gets or sets the object position in pixels relative to the left edge of the document.
        /// </summary>
        [Bindable(false), Category("Appearance"),
        Description("Object position in pixels relative to the left edge of the document.")]
        [DefaultValue(50)]
        public virtual int PopupLeftPx
        {
            get { return this.popupLeftPx; }
            set
            {
                this.popupLeftPx = value;
                this.div.Style["left"] = value.ToString() + "px";
            }
        }

        /// <summary>
        /// Gets or sets the object position in pixels relative to the top edge of the document.
        /// </summary>
        [Bindable(false), Category("Appearance"),
        Description("Object position in pixels relative to the top edge of the document.")]
        [DefaultValue(50)]
        public virtual int PopupTopPx
        {
            get { return this.popupTopPx; }
            set
            {
                this.popupTopPx = value;
                this.div.Style["top"] = value.ToString() + "px";
            }
        }

		/// <summary>
		/// Gets or sets the URL of the popup page which performs the lookup action.
		/// </summary>
		[Bindable(false), Category("Behavior"),
		Description("URL of the popup page which performs the lookup action.")]
        [DefaultValue("")]
		public virtual string PopupUrl
		{
            get { return this.txtPopupUrl.Text; }
            set { this.txtPopupUrl.Text = (value != null) ? value.Trim() : ""; }
		}

		/// <summary>
		/// Gets or sets the width of the popup window/iframe.
		/// </summary>
		[Bindable(false), Category("Appearance"),
		Description("Width of the popup window/iframe.")]
        [DefaultValue(typeof(Unit), "600px")]
		public virtual Unit PopupWidth
		{
			get { return Unit.Parse(this.iframe.Attributes["width"]); }
			set { this.iframe.Attributes["width"] = value.ToString(); }
		}		

		/// <summary>
		/// Gets or sets the text caption displayed in the button control.
		/// </summary>
		[Bindable(true), Category("Appearance"),
		Description("Text caption displayed in the button control.")]
        [DefaultValue("")]
        [Localizable(true)]
		public string Text
		{
			get { return this.lookupBtn.Value; }
			set { this.lookupBtn.Value = value; }
		}

		/// <summary>
		/// Gets or sets the text displayed when the mouse pointer hovers over the Web server control.
		/// </summary>
		[Bindable(true), Category("Behavior"),
		Description("Text displayed when the mouse pointer hovers over the Web server control.")]
        [DefaultValue("")]
        [Localizable(true)]
		public override string ToolTip
		{
			get { return this.lookupBtn.Attributes["title"] ?? ""; }
			set	{ this.lookupBtn.Attributes["title"] = value; }
		}
	
		/// <summary>
		/// Gets or sets the width of the button.
		/// </summary>
		[Bindable(false), Category("Appearance"),
		Description("Width of the button.")]
		public override Unit Width
		{
			get { return Unit.Parse(this.lookupBtn.Style["width"]); }
			set { this.lookupBtn.Style["width"] = value.ToString(); }
		}

        string itemDelimiter = MultiChoiceCommand.DefaultDelimiter;

        /// <summary>
        /// Gets or sets the string which delimites values when multiple items are selected.
        /// </summary>
        /// <value>A string with minimum length of 1. Default is semicolon (<b>";"</b>).</value>
        [Category("Behavior")]
        [DefaultValue(MultiChoiceCommand.DefaultDelimiter)]
        [Description("Delimites values when multiple items are selected.")]
        public virtual string ItemDelimiter
        {
            get { return this.itemDelimiter; }
            set { this.itemDelimiter = value; }
        }

		#endregion

        #region Client-side function names.

        /// <summary>
        /// Gets the name of the client-side script which hides the popup window.
        /// </summary>
        /// <remarks>Hide function requires no parameters.</remarks>
        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public string HideLookupJScript
		{
			get {return this.ID + "__Hide";}
		}

        /// <summary>
        /// Gets the name of the client-side script which is used to set the value selected by the popup
        /// window into <b>PopupButton</b> control.
        /// </summary>
        /// <remarks><para>The generated script requires exactly one parameter. The parameter sets the selected
        /// value into <b>PopupButton</b> control. The script then initiates a <b>PostBack</b> and
        /// raises the <see cref="ItemSelected"/> event on the server.</para>
        /// </remarks>
        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public string SetSelectedValueFunctionName
		{
			get {return this.ID + "__SetVal";}
        }

        #endregion
       
        #region ItemSelected event.

        /// <summary>
		/// Occurs when the popup/lookup selects a value and sets it into <see cref="PopupButton"/> control by
		/// <see cref="SetSelectedValueFunctionName"/>. Selected value is sent to code-behind event handler
		/// in the <see cref="System.Web.UI.WebControls.CommandEventArgs.CommandArgument"/> object.
		/// </summary>
        /// <remarks>If multiple items are selected, the values are delimited with <see cref="ItemDelimiter"/>.</remarks>
        [Description("Occurs when the popup/lookup selects a value.")]
		public event CommandEventHandler ItemSelected;

		private void FireEventIfRowSelected(object sender, EventArgs e)
		{
			// Called in OnLoad event.

			// Only fire if there is a value in the hidden field.
            if (string.IsNullOrEmpty(this.selectedValueTxt.Text))
                return;

			if (this.ItemSelected != null)
				this.ItemSelected(this, new CommandEventArgs("ItemSelected", this.selectedValueTxt.Text));

			// Clear container.
			this.selectedValueTxt.Text = "";
        }

        #endregion

        #region Render.

        /// <summary>
		/// Sends server control content to a provided <see cref="HtmlTextWriter"/> object, which writes the content to be rendered on the client.
		/// </summary>
		/// <param name="output">The <b>HtmlTextWriter</b> object that receives the server control content.</param>
		protected override void Render(HtmlTextWriter output)
		{
			// By now popup URL has been set.
            if (!string.IsNullOrEmpty(this.txtPopupUrl.Text))
			{
				if (this.lookupBtn.Attributes["onclick"] != null)
					this.lookupBtn.Attributes["onclick"] = RenderDisplayLookupScriptBody();
				else
					this.lookupBtn.Attributes.Add("onclick", RenderDisplayLookupScriptBody());
			}

            this.txtPopupUrl.Text = WebUtil.ResolveUrl(this.txtPopupUrl.Text);
			EmitJavaScript(output);
            base.Render(output);	
		}

        private void EmitJavaScript(HtmlTextWriter output)
        {
            output.Write("<script type=\"text/javascript\">");
            output.Write(output.NewLine);
            output.Write(RenderHideLookupScript());
            output.Write(output.NewLine);
            output.Write(RenderSetSelectedValueScript());
            output.Write(output.NewLine);
            output.Write("</script>");
        }

		private string RenderSetSelectedValueScript()
		{
			// Script that is invoked when lookup returns a value. The parent form is submitted. 
			// RowSelected event handlers process the selected value.
            StringBuilder script = new StringBuilder();

            // Function signature: function ID__SetVal(sValue)
            script.Append("function ");
            script.Append(this.SetSelectedValueFunctionName);
            script.Append("(sValue) ");

            // Begin function.
            script.Append("{ ");

            // The value is stored in the hidden field: document.getElementById('selectedValueTxt').value = sValue;
            script.Append("document.getElementById('");
            script.Append(this.selectedValueTxt.ClientID);
            script.Append("').value = sValue; ");

            // Hide lookup after the value has been set: ID__Hide();
            script.Append(this.HideLookupJScript);
            script.Append("(); ");

            // Auto-postback: document.getElementById('autoSubmitBtn').click();
            script.Append("document.getElementById('");
            script.Append(this.autoSubmitBtn.ClientID);
            script.Append("').click();");

            // End of function.
            script.Append("}");
            return script.ToString();
		}

        const string displayLookupCallFormat = "Biz_BoundLookup_Display('{0}', '{1}', '{2}', '{3}', '{4}', '{5}', '{6}', {7}, {8}); ";

		private string RenderDisplayLookupScriptBody()
		{
            string script = string.Format(displayLookupCallFormat, this.selectedValueTxt.ClientID, this.ClientID,
                this.div.ClientID, this.iframe.ClientID, this.txtPopupUrl.ClientID, "", "", this.PopupTopPx, this.PopupLeftPx);
            return script;
		}

        const string hideLookupCallFormat = "Biz_BoundLookup_Hide('{0}', '{1}', '{2}'); ";

		private string RenderHideLookupScript()
		{
            StringBuilder script = new StringBuilder();
            script.Append("function ");
            script.Append(this.HideLookupJScript);
            script.Append("(){ ");
            script.Append(string.Format(hideLookupCallFormat, this.ClientID, this.div.ClientID, this.iframe.ClientID));
            script.Append("} ");

            return script.ToString();
		}

		internal string GetDesignTimeHtml()
		{
            System.IO.StringWriter strWriter = new System.IO.StringWriter();
            HtmlTextWriter htmWriter = new HtmlTextWriter(strWriter);
            this.lookupBtn.RenderControl(htmWriter);
            string designTimeHtml = strWriter.GetStringBuilder().ToString();

            return designTimeHtml;
		}

		#endregion

        #region ISearchPageClient.

        /// <summary>
        /// Creates a command which accepts the selected search/lookup values and sets them into the client component.
        /// </summary>
        /// <param name="calledFromPopupWindow">Indicates whether the provider is rendered in a popup/iframe window.</param>
        /// <param name="text">Command text.</param>
        /// <param name="requiredParameters">Parameters (ie. data fields) required for the accept command.</param>
        /// <returns><b>IWebCommand</b> object or <b>null</b> is the client doesn't support the accept command.</returns>
        public IWebCommand CreateAcceptCommand(bool calledFromPopupWindow, string text, string[] requiredParameters)
        {
            if (requiredParameters == null || requiredParameters.Length != 1)
                throw new ArgumentException("Exactly one parameter is required.", "requiredParameters");

            StringBuilder callFormat = new StringBuilder();
            if (calledFromPopupWindow)
                callFormat.Append("parent.");
            callFormat.Append(this.SetSelectedValueFunctionName);
            callFormat.Append("('{0}')");

            MultiChoiceCommand selectItems = new MultiChoiceCommand(null, text, callFormat.ToString(), requiredParameters);
            selectItems.Delimiter = this.ItemDelimiter;
            return selectItems;
        }

        /// <summary>
        /// Creates a command which cancels the search/lookup action.
        /// </summary>
        /// <param name="calledFromPopupWindow">Indicates whether the provider is rendered in a popup/iframe window.</param>
        /// <param name="text">Command text.</param>
        /// <returns><b>IWebCommand</b> object.</returns>
        public IWebCommand CreateCancelCommand(bool calledFromPopupWindow, string text)
        {
            StringBuilder functionCall = new StringBuilder();
            if (calledFromPopupWindow)
                functionCall.Append("parent.");
            functionCall.Append(this.HideLookupJScript);
            functionCall.Append("()");

            return new JsCommand(null, text, functionCall.ToString());
        }

        /// <summary>
        /// Has no meaning. The provider key must be specified via <see cref="ISearchPageClient.SearchPageUrl"/> property.
        /// </summary>
        [Browsable(false)]
        string ISearchClient.SearchProviderKey
        {
            get { return null; }
            set { }
        }

        /// <summary>
        /// Gets or sets the URL of a page which uses/renders the registered <see cref="SearchProvider"/>.
        /// </summary>
        /// <remarks>URL must contain a query string value which specifies the provider key.</remarks>
        [Browsable(false)]
        string ISearchPageClient.SearchPageUrl
        {
            get { return this.PopupUrl; }
            set { this.PopupUrl = value; }
        }

        #endregion        
    }
}
